#ifndef POINT_BUFFER_H
#define POINT_BUFFER_H

#include "vector4.h"
#include "opengl_inc.h"
#include "shadermanager.h"
#include <vector>

class OGLPointBuffer
{
public:
  OGLPointBuffer() : buffer_size(0), initialized(false) {}
  ~OGLPointBuffer()
  {
    if (initialized)
    {
      glDeleteBuffers(1, &pointBuffer);
      glDeleteBuffers(1, &colorBuffer);
      glDeleteBuffers(1, &idsBuffer);
      glDeleteVertexArrays(1, &vao);
      glDeleteVertexArrays(1, &selvao);
    }
  }

  void initPoints() { type = GL_POINTS; }
  void initLines() { type = GL_LINES; }
  void addPoint(const Vector3f& vertex, const Vector4f& color, const GLuint i)
  {
    points.push_back(Vector4f(vertex));
    colors.push_back(color);
    ids.push_back(i);
  }
  void addLine(const Vector3f& vertex1, const Vector4f& color1, const Vector3f& vertex2, const Vector4f& color2, const GLuint i)
  {
    points.push_back(Vector4f(vertex1));
    colors.push_back(color1);
    points.push_back(Vector4f(vertex2));
    colors.push_back(color2);
    ids.push_back(i);
  }
  void init()
  {
    initialized = true;
    glGenVertexArrays(1, &vao);
    glGenVertexArrays(1, &selvao);
    glGenBuffers(1, &pointBuffer);
    glGenBuffers(1, &colorBuffer);
    glGenBuffers(1, &idsBuffer);
    initVao();
  }
  void clear()
  {
    points.clear();
    colors.clear();
  }
  void uploadData()
  {
    if (buffer_size < points.size())
    {
      glBindBuffer(GL_ARRAY_BUFFER, pointBuffer);
      glBufferData(GL_ARRAY_BUFFER, points.size() * sizeof(Vector4f), &(points[0]), GL_STATIC_DRAW);
      glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
      glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(Vector4f), &(colors[0]), GL_STATIC_DRAW);
      glBindBuffer(GL_ARRAY_BUFFER, idsBuffer);
      glBufferData(GL_ARRAY_BUFFER, ids.size() * sizeof(GLuint), &(ids[0]), GL_STATIC_DRAW);
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      buffer_size = points.size();
    } else {
      glBindBuffer(GL_ARRAY_BUFFER, pointBuffer);
      glBufferSubData(GL_ARRAY_BUFFER, 0, points.size() * sizeof(Vector4f), &(points[0]));
      glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
      glBufferSubData(GL_ARRAY_BUFFER, 0, colors.size() * sizeof(Vector4f), &(colors[0]));
      glBindBuffer(GL_ARRAY_BUFFER, idsBuffer);
      glBufferSubData(GL_ARRAY_BUFFER, 0, ids.size() * sizeof(GLuint), &(ids[0]));
      glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
  }
  void draw()
  {
    if (hasPrimitives())
    {
      glBindVertexArray(vao);
      glDrawArrays(type, 0, points.size());
      glBindVertexArray(0);
    }
  }
  void drawSelection()
  {
    if (hasPrimitives())
    {
      glBindVertexArray(selvao);
      glDrawArrays(type, 0, points.size());
      glBindVertexArray(0);
    }
  }
  bool hasPrimitives() const { return points.size(); }
  GLenum mode() const { return type; }
private:
  void initVao()
  {
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
    glVertexAttribPointer(OGLShaderManager::MAT_AMBIENT, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(OGLShaderManager::MAT_AMBIENT);
    glBindBuffer(GL_ARRAY_BUFFER, pointBuffer);
    glVertexAttribPointer(OGLShaderManager::VERTEX, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(OGLShaderManager::VERTEX);

    glBindVertexArray(selvao);
    glBindBuffer(GL_ARRAY_BUFFER, idsBuffer);
    glVertexAttribIPointer(OGLShaderManager::ID, 1, GL_UNSIGNED_INT, 0, 0);
    glEnableVertexAttribArray(OGLShaderManager::ID);
    glBindBuffer(GL_ARRAY_BUFFER, pointBuffer);
    glVertexAttribPointer(OGLShaderManager::VERTEX, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(OGLShaderManager::VERTEX);
    glBindVertexArray(0);
  }

  GLuint vao, selvao, pointBuffer, colorBuffer, idsBuffer;
  std::vector<Vector4f> points;
  std::vector<Vector4f> colors;
  std::vector<GLuint> ids;
  GLenum type;
  uint buffer_size;
  bool initialized;
};

#endif // POINT_BUFFER_H
